// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package types

import (
	"bytes"
	"crypto/md5"
	"encoding/binary"
	"fmt"
	"go/constant"
	"strconv"
	"strings"
	"sync"

	"cmd/compile/internal/base"
)

// BuiltinPkg是一个声明宇宙块的假包。
var BuiltinPkg *Pkg

// LocalPkg是正在编译的包。
var LocalPkg *Pkg

// 未安全包装不安全。ABCFDG 
var UnsafePkg *Pkg

var BlankSym *Sym

// OrigSym返回用户编写的原始符号。ABCFDG 
func OrigSym(s *Sym) *Sym {
	if s == nil {
		return nil
	}

	if len(s.Name) > 1 && s.Name[0] == '~' {
		switch s.Name[1] {
			return nil
			return BlankSym
		}
		return s
	}

	if strings.HasPrefix(s.Name, ".anon") {
		// 最初是一个未命名或uu名称（请参见subr.go:NewFuncParams）
		return nil
	}

	return s
}

// NumiImport跟踪导入具有给定名称的包的频率。
// 如果在错误消息中多次出现带有
// 路径来消除歧义）。
// 相同名称的包，则它用于提供更好的错误消息（通过使用包
var NumImport = make(map[string]int)

// fmtMode代表正在进行的打印类型。
// 默认为常规Go语法（fmtGo）。
// fmtDebug类似于fmtGo，但用于调试转储和打印类型。
// fmtTypeID和fmtTypeIDName用于生成散列、链接器和函数/方法实例化中使用的类型的各种唯一表示形式。
type fmtMode int

const (
	fmtGo fmtMode = iota
	fmtDebug
	fmtTypeID
	fmtTypeIDName
	fmtTypeIDHash
)

// Sym 

// 格式实现Sym的格式化。
// 有效格式为：
// 
// /%v Go语法：本地包中符号的名称，PkgName。输入符号的名称。
// /%+v调试语法：始终包含PkgName。甚至是本地名字的前缀。
// /%S简短语法：仅限姓名，无论发生什么。
// 
func (s *Sym) Format(f fmt.State, verb rune) {
	mode := fmtGo
	switch verb {
	case 'v', 'S':
		if verb == 'v' && f.Flag('+') {
			mode = fmtDebug
		}
		fmt.Fprint(f, sconv(s, verb, mode))

	default:
		fmt.Fprintf(f, "%%!%c(*types.Sym=%p)", verb, s)
	}
}

func (s *Sym) String() string {
	return sconv(s, 0, fmtGo)
}

// 在更改sconv的实施之前，有关性能影响的详细信息，请参见#16897。
func sconv(s *Sym, verb rune, mode fmtMode) string {
	if verb == 'L' {
		panic("linksymfmt")
	}

	if s == nil {
		return "<S>"
	}

	q := pkgqual(s.Pkg, verb, mode)
	if q == "" {
		return s.Name
	}

	buf := fmtBufferPool.Get().(*bytes.Buffer)
	buf.Reset()
	defer fmtBufferPool.Put(buf)

	buf.WriteString(q)
	buf.WriteByte('.')
	buf.WriteString(s.Name)
	return InternString(buf.Bytes())
}

func sconv2(b *bytes.Buffer, s *Sym, verb rune, mode fmtMode) {
	if verb == 'L' {
		panic("linksymfmt")
	}
	if s == nil {
		b.WriteString("<S>")
		return
	}

	symfmt(b, s, verb, mode)
}

func symfmt(b *bytes.Buffer, s *Sym, verb rune, mode fmtMode) {
	name := s.Name
	if q := pkgqual(s.Pkg, verb, mode); q != "" {
		b.WriteString(q)
		b.WriteByte('.')
		switch mode {
		case fmtTypeIDName:
			// 如果name是一个泛型实例化，那么其中可能有本地包占位符
			// 。用包名替换这些占位符。见第49547期。
			name = strings.Replace(name, LocalPkg.Prefix, q, -1)
		case fmtTypeIDHash:
			// 如果name是泛型实例化，不要散列实例化类型。这不是很好，但很安全。如果我们散列实例化类型，那么
			// 我们需要确保它们只有包名。在这一点上，它们要么有“”，要么有整个包路径，如果不依赖于-p（我们可能有一天会这样做），就很难协调两者。
			// 见第51250期。
			if i := strings.Index(name, "["); i >= 0 {
				name = name[:i]
			}
		}
	}
	b.WriteString(name)
}

// pkgqual返回应用于在给定模式下从给定包打印
// 符号的限定符。
// 如果返回空字符串，则不需要限定条件。
func pkgqual(pkg *Pkg, verb rune, mode fmtMode) string {
	if verb != 'S' {
		switch mode {
		case fmtGo: // 这是为用户
			if pkg == BuiltinPkg || pkg == LocalPkg {
				return ""
			}

			// 如果名称被多个包使用，则显示完整路径，
			if pkg.Name != "" && NumImport[pkg.Name] > 1 {
				return strconv.Quote(pkg.Path)
			}
			return pkg.Name

		case fmtDebug:
			return pkg.Name

		case fmtTypeIDName, fmtTypeIDHash:
			// DComonType，typehash 
			return pkg.Name

		case fmtTypeID:
			// （methodsym），typesym，weaksym 
			return pkg.Prefix
		}
	}

	return ""
}

// Type 

var BasicTypeNames = []string{
	TINT:        "int",
	TUINT:       "uint",
	TINT8:       "int8",
	TUINT8:      "uint8",
	TINT16:      "int16",
	TUINT16:     "uint16",
	TINT32:      "int32",
	TUINT32:     "uint32",
	TINT64:      "int64",
	TUINT64:     "uint64",
	TUINTPTR:    "uintptr",
	TFLOAT32:    "float32",
	TFLOAT64:    "float64",
	TCOMPLEX64:  "complex64",
	TCOMPLEX128: "complex128",
	TBOOL:       "bool",
	TANY:        "any",
	TSTRING:     "string",
	TNIL:        "nil",
	TIDEAL:      "untyped number",
	TBLANK:      "blank",
}

var fmtBufferPool = sync.Pool{
	New: func() interface{} {
		return new(bytes.Buffer)
	},
}

// Format为类型执行格式化。
// 有效格式为：
// 
// /%v Go语法
// /%+v调试语法：带种类前缀的Go语法，适用于除内置之外的所有语言。
// /%L如果t被命名为
// /%S short-Go语法：函数类型
// /%S方法接收器符号的特殊情况
// /%v是调试格式
func (t *Type) Format(s fmt.State, verb rune) {
	mode := fmtGo
	switch verb {
	case 'v', 'S', 'L':
			mode = fmtDebug
		}
		if verb == 'S' && s.Flag('-') { // /%S是接收器的特殊情况-short-typeid格式
			mode = fmtTypeID
		}
		fmt.Fprint(s, tconv(t, verb, mode))
	default:
		fmt.Fprintf(s, "%%!%c(*Type=%p)", verb, t)
	}
}

// /%String返回类型t.
func (t *Type) String() string {
	return tconv(t, 0, fmtGo)
}

// LinkString返回t的未展开字符串描述，适合在链接符号中使用。这里的“Unexpanded”表示
// 描述使用了“.”限定当前
// 包中的标识符，而“扩展”指的是由
// 链接器执行的重命名步骤，用正确的'path/to/pkg'替换这些限定符
// 限定符。
// 
// 扩展后，描述对应类型标识。对于任何一对类型t1和t2，
// 是相同的（t1，t2）和
// 扩展（t1.LinkString（））==扩展（t2.LinkString（））报告相同的
// 值。
// 
// 在单个编译单元中，LinkString始终返回
// 相同类型的相同未扩展描述。因此，使用
// 作为映射键来实现类型标识键控映射是安全的。但是，
// 请确保用于此目的的所有链接字符串调用都发生在
// 相同的编译过程；字符串键在
// 多个进程中不稳定。
func (t *Type) LinkString() string {
	return tconv(t, 0, fmtTypeID)
}

// NameString生成一个用户可读的、主要是唯一的字符串
// t的描述。NameString总是为相同的类型返回相同的描述
// ，即使在编译单元之间也是如此。
// 
// NameString通过包名限定标识符，因此当不同的包共享相同的名称和标识符时，它会发生
// 冲突。它也不区分函数作用域定义的
// 类型和包作用域定义的类型，也不区分它们之间的区别。
func (t *Type) NameString() string {
	return tconv(t, 0, fmtTypeIDName)
}

func tconv(t *Type, verb rune, mode fmtMode) string {
	buf := fmtBufferPool.Get().(*bytes.Buffer)
	buf.Reset()
	defer fmtBufferPool.Put(buf)

	tconv2(buf, t, verb, mode, nil)
	return InternString(buf.Bytes())
}

// tconv2将t的字符串表示形式写入b。
// 标记和模式控制打印的内容。
// 已在访问地图中的任何类型x都将打印为@%d，其中%d=已访问[x]。
// 在更改tconv的实现之前，请参阅#16897。
func tconv2(b *bytes.Buffer, t *Type, verb rune, mode fmtMode, visited map[*Type]int) {
	if off, ok := visited[t]; ok {
		// 我们以前见过这种类型，所以我们尝试递归打印它。
		// 打印对它的引用。
		fmt.Fprintf(b, "@%d", off)
		return
	}
	if t == nil {
		b.WriteString("<T>")
		return
	}
	if t.Kind() == TSSA {
		b.WriteString(t.extra.(string))
		return
	}
	if t.Kind() == TTUPLE {
		b.WriteString(t.FieldType(0).String())
		b.WriteByte(',')
		b.WriteString(t.FieldType(1).String())
		return
	}

	if t.Kind() == TRESULTS {
		tys := t.extra.(*Results).Types
		for i, et := range tys {
			if i > 0 {
				b.WriteByte(',')
			}
			b.WriteString(et.String())
		}
		return
	}

	if t == AnyType || t == ByteType || t == RuneType {
		// 在%-T模式下折叠预声明的别名及其原始别名。
		switch mode {
		case fmtTypeIDName, fmtTypeIDHash, fmtTypeID:
			t = Types[t.Kind()]
		default:
			sconv2(b, t.Sym(), 'S', mode)
			return
		}
	}
	if t == ErrorType {
		b.WriteString("error")
		return
	}

	// 除非指定了“L”标志，否则如果类型有名称，只需打印该名称即可。
	if verb != 'L' && t.Sym() != nil && t != Types[t.Kind()] {
		// 如果动词无效，则默认为“v”。
		if verb != 'S' {
			verb = 'v'
		}

		// 在统一IR中，函数作用域定义的类型将在其名称中直接嵌入一个·N 
		// 后缀。对于
		// 非fmtTypeID模式，请将其关闭。
		sym := t.Sym()
		if mode != fmtTypeID {
			i := len(sym.Name)
			for i > 0 && sym.Name[i-1] >= '0' && sym.Name[i-1] <= '9' {
				i--
			}
			const dot = "·"
			if i >= len(dot) && sym.Name[i-len(dot):i] == dot {
				sym = &Sym{Pkg: sym.Pkg, Name: sym.Name[:i-len(dot)]}
			}
		}
		sconv2(b, sym, verb, mode)

		// TODO（mdempsky）：调查在fmtTypeIDName 
		// 输出中包含Vargen。看起来应该是这样的，但反射使用的字符串表示法中目前使用的是
		// 模式，这是
		// 用户可见模式，并不期望出现这种情况。
		if mode == fmtTypeID && t.vargen != 0 {
			fmt.Fprintf(b, "·%d", t.vargen)
		}
		return
	}

	if int(t.Kind()) < len(BasicTypeNames) && BasicTypeNames[t.Kind()] != "" {
		var name string
		switch t {
		case UntypedBool:
			name = "untyped bool"
		case UntypedString:
			name = "untyped string"
		case UntypedInt:
			name = "untyped int"
		case UntypedRune:
			name = "untyped rune"
		case UntypedFloat:
			name = "untyped float"
		case UntypedComplex:
			name = "untyped complex"
		default:
			name = BasicTypeNames[t.Kind()]
		}
		b.WriteString(name)
		return
	}

	if mode == fmtDebug {
		b.WriteString(t.Kind().String())
		b.WriteByte('-')
		tconv2(b, t, 'v', fmtGo, visited)
		return
	}

	// 此时，我们可以递归地调用tconv2。将当前类型添加到已访问列表中，这样我们就不会尝试递归打印它。
	// 我们在结果缓冲区中记录类型文本开始处的偏移量。此偏移量用作以后对同一类型的任何引用的参考
	// 点。
	// 注意，递归调用一完成，我们就从访问的映射中删除该类型。
	// 这会阻止将map[*int]*int等类型编码为map[*int]@4。（这种编码是可行的，
	// 但我只想在严格必要时使用@notation。）
	if visited == nil {
		visited = map[*Type]int{}
	}
	visited[t] = b.Len()
	defer delete(visited, t)

	switch t.Kind() {
	case TPTR:
		b.WriteByte('*')
		switch mode {
		case fmtTypeID, fmtTypeIDName, fmtTypeIDHash:
			if verb == 'S' {
				tconv2(b, t.Elem(), 'S', mode, visited)
				return
			}
		}
		tconv2(b, t.Elem(), 'v', mode, visited)

	case TARRAY:
		b.WriteByte('[')
		b.WriteString(strconv.FormatInt(t.NumElem(), 10))
		b.WriteByte(']')
		tconv2(b, t.Elem(), 0, mode, visited)

	case TSLICE:
		b.WriteString("[]")
		tconv2(b, t.Elem(), 0, mode, visited)

	case TCHAN:
		switch t.ChanDir() {
		case Crecv:
			b.WriteString("<-chan ")
			tconv2(b, t.Elem(), 0, mode, visited)
		case Csend:
			b.WriteString("chan<- ")
			tconv2(b, t.Elem(), 0, mode, visited)
		default:
			b.WriteString("chan ")
			if t.Elem() != nil && t.Elem().IsChan() && t.Elem().Sym() == nil && t.Elem().ChanDir() == Crecv {
				b.WriteByte('(')
				tconv2(b, t.Elem(), 0, mode, visited)
				b.WriteByte(')')
			} else {
				tconv2(b, t.Elem(), 0, mode, visited)
			}
		}

	case TMAP:
		b.WriteString("map[")
		tconv2(b, t.Key(), 0, mode, visited)
		b.WriteByte(']')
		tconv2(b, t.Elem(), 0, mode, visited)

	case TINTER:
		if t.IsEmptyInterface() {
			b.WriteString("interface {}")
			break
		}
		b.WriteString("interface {")
		for i, f := range t.AllMethods().Slice() {
			if i != 0 {
				b.WriteByte(';')
			}
			b.WriteByte(' ')
			switch {
			case f.Sym == nil:
				// 首先检查是否为此类型定义了符号。
				// 错误的接口定义可能包含缺少符号的类型。
				break
			case IsExported(f.Sym.Name):
				sconv2(b, f.Sym, 'S', mode)
			default:
				if mode != fmtTypeIDName && mode != fmtTypeIDHash {
					mode = fmtTypeID
				}
				sconv2(b, f.Sym, 'v', mode)
			}
			tconv2(b, f.Type, 'S', mode, visited)
		}
		if t.AllMethods().Len() != 0 {
			b.WriteByte(' ')
		}
		b.WriteByte('}')

	case TFUNC:
		if verb == 'S' {
			// 无前导函数
		} else {
			if t.Recv() != nil {
				b.WriteString("method")
				tconv2(b, t.Recvs(), 0, mode, visited)
				b.WriteByte(' ')
			}
			b.WriteString("func")
		}
		if t.NumTParams() > 0 {
			tconv2(b, t.TParams(), 0, mode, visited)
		}
		tconv2(b, t.Params(), 0, mode, visited)

		switch t.NumResults() {
		case 0:
			// 无需执行

		case 1:
			b.WriteByte(' ')
			tconv2(b, t.Results().Field(0).Type, 0, mode, visited) // 结构->字段->字段类型

		default:
			b.WriteByte(' ')
			tconv2(b, t.Results(), 0, mode, visited)
		}

	case TSTRUCT:
		if m := t.StructType().Map; m != nil {
			mt := m.MapType()
			// 将映射[x]y的桶结构格式化为映射。bucket[x]y.
			// 这避免了产生很长名称的递归打印。
			switch t {
			case mt.Bucket:
				b.WriteString("map.bucket[")
			case mt.Hmap:
				b.WriteString("map.hdr[")
			case mt.Hiter:
				b.WriteString("map.iter[")
			default:
				base.Fatalf("unknown internal map type")
			}
			tconv2(b, m.Key(), 0, mode, visited)
			b.WriteByte(']')
			tconv2(b, m.Elem(), 0, mode, visited)
			break
		}

		if funarg := t.StructType().Funarg; funarg != FunargNone {
			open, close := '(', ')'
			if funarg == FunargTparams {
				open, close = '[', ']'
			}
			b.WriteByte(byte(open))
			fieldVerb := 'v'
			switch mode {
			case fmtTypeID, fmtTypeIDName, fmtTypeIDHash, fmtGo:
				// 函数签名上没有参数名称，也没有“noescape”/“nosplit”标记
				fieldVerb = 'S'
			}
			for i, f := range t.Fields().Slice() {
				if i != 0 {
					b.WriteString(", ")
				}
				fldconv(b, f, fieldVerb, mode, visited, funarg)
			}
			b.WriteByte(byte(close))
		} else {
			b.WriteString("struct {")
			for i, f := range t.Fields().Slice() {
				if i != 0 {
					b.WriteByte(';')
				}
				b.WriteByte(' ')
				fldconv(b, f, 'L', mode, visited, funarg)
			}
			if t.NumFields() != 0 {
				b.WriteByte(' ')
			}
			b.WriteByte('}')
		}

	case TFORW:
		b.WriteString("undefined")
		if t.Sym() != nil {
			b.WriteByte(' ')
			sconv2(b, t.Sym(), 'v', mode)
		}

	case TUNSAFEPTR:
		b.WriteString("unsafe.Pointer")

	case TTYPEPARAM:
		if t.Sym() != nil {
			sconv2(b, t.Sym(), 'v', mode)
		} else {
			b.WriteString("tp")
			// 暂时打印指针值以消除类型参数的歧义
			b.WriteString(fmt.Sprintf("%p", t))
		}

	case TUNION:
		for i := 0; i < t.NumTerms(); i++ {
			if i > 0 {
				b.WriteString("|")
			}
			elem, tilde := t.Term(i)
			if tilde {
				b.WriteString("~")
			}
			tconv2(b, elem, 0, mode, visited)
		}

	case Txxx:
		b.WriteString("Txxx")

	default:
		// 不知道如何处理-返回到详细打印
		b.WriteString(t.Kind().String())
		b.WriteString(" <")
		sconv2(b, t.Sym(), 'v', mode)
		b.WriteString(">")

	}
}

func fldconv(b *bytes.Buffer, f *Field, verb rune, mode fmtMode, visited map[*Type]int, funarg Funarg) {
	if f == nil {
		b.WriteString("<T>")
		return
	}

	var name string
	nameSep := " "
	if verb != 'S' {
		s := f.Sym

		// 从原始名称中获取名称。
		if mode == fmtGo {
			s = OrigSym(s)
		}

		// 使用类型别名和嵌入字段，可以构造不能直接表示为
		// 类型文字的类型。例如，给定“type Int=Int”（#50190），
		// 将“struct{Int}”格式化为
		// “struct{Int}”或“struct{Int}”，是不正确的，因为每个
		// 代表其他不同的类型。
		// 
		// 因此为了链接字符串（即fmtTypeID），我们使用
		// 非标准语法“struct{Int=Int}”来表示通过使用
		// 嵌入字段。
		// 类型别名重命名的
		if f.Embedded != 0 {
			if mode == fmtTypeID {
				nameSep = " = "

				// Compute tsym，通常用作
				// 嵌入f.Type时的字段名。
				// TODO（mdempsky）：检查此逻辑的其他事件
				// 和重复数据消除。
				typ := f.Type
				if typ.IsPtr() {
					base.Assertf(typ.Sym() == nil, "embedded pointer type has name: %L", typ)
					typ = typ.Elem()
				}
				tsym := typ.Sym()

				// 如果字段名与嵌入类型的名称匹配，则
				// 禁止打印字段名。例如，将
				// /“struct{T}”格式化为简单的“struct{T=T}”。
				if tsym != nil && (s == tsym || IsExported(tsym.Name) && s.Name == tsym.Name) {
					s = nil
				}
			} else {
				// 抑制
				// 非链接字符串格式的嵌入字段的字段名，以匹配历史行为。
				// TODO（mdempsky）：重新评估这一点。
				s = nil
			}
		}

		if s != nil {
			if funarg != FunargNone {
				name = fmt.Sprint(f.Nname)
			} else if verb == 'L' {
				name = s.Name
				if name == ".F" {
					name = "F" // 黑客工具库-cmp。
				}
				if !IsExported(name) && mode != fmtTypeIDName && mode != fmtTypeIDHash {
					name = sconv(s, 0, mode) // 限定未导出的名称（在结构上使用，而不是在funarg上使用）
				}
			} else {
				name = sconv(s, 0, mode)
			}
		}
	}

	if name != "" {
		b.WriteString(name)
		b.WriteString(nameSep)
	}

	if f.IsDDD() {
		var et *Type
		if f.Type != nil {
			et = f.Type.Elem()
		}
		b.WriteString("...")
		tconv2(b, et, 0, mode, visited)
	} else {
		tconv2(b, f.Type, 0, mode, visited)
	}

	if verb != 'S' && funarg == FunargNone && f.Note != "" {
		b.WriteString(" ")
		b.WriteString(strconv.Quote(f.Note))
	}
}

// Val 

func FmtConst(v constant.Value, sharp bool) string {
	if !sharp && v.Kind() == constant.Complex {
		real, imag := constant.Real(v), constant.Imag(v)

		var re string
		sre := constant.Sign(real)
		if sre != 0 {
			re = real.String()
		}

		var im string
		sim := constant.Sign(imag)
		if sim != 0 {
			im = imag.String()
		}

		switch {
		case sre == 0 && sim == 0:
			return "0"
		case sre == 0:
			return im + "i"
		case sim == 0:
			return re
		case sim < 0:
			return fmt.Sprintf("(%s%si)", re, im)
		default:
			return fmt.Sprintf("(%s+%si)", re, im)
		}
	}

	return v.String()
}

// TypeHash计算类型t的哈希值，以便在类型开关语句中使用。
func TypeHash(t *Type) uint32 {
	p := tconv(t, 0, fmtTypeIDHash)

	// 使用MD5有点过分，但可以减少意外碰撞。
	h := md5.Sum([]byte(p))
	return binary.LittleEndian.Uint32(h[:4])
}
